# 01 导入gurobi和其它python库，初始化给定数据的数据结构
from itertools import product
from math import sqrt

import gurobipy as gp
from gurobipy import GRB

customers = [(0, 1.5), (2.5, 1.2)]
facilities = [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]
setup_cost = [3, 2, 3, 1, 3, 3, 4, 3, 2]
cost_per_mile = 1


# 02 预处理
# 定义一个函数，用于计算每个设施和客户之间的欧式距离，获取MIP模型需要的关键参数

# 计算两个地方的欧式距离
def compute_distance(loc1, loc2):
    dx = loc1[0] - loc2[0]
    dy = loc1[1] - loc2[1]
    return sqrt(dx * dx + dy * dy)


num_facilities = len(facilities)
num_customers = len(customers)
cartesian_prod = list(product(range(num_customers), range(num_facilities)))

# 每对客户和设施的运输成本
shipping_cost = {(c, f): cost_per_mile * compute_distance(customers[c], facilities[f])
                 for c, f in cartesian_prod}
# 打印预处理的结果
print(shipping_cost)

# 03 模型部署
# 现在定义设施选址问题的MIP模型，包括决策变量，约束和目标函数。
# 然后开始优化过程，Gurobi找到能最小化总成本的修建方案

# 模型
m = gp.Model('facility_location')

# 两个决策变量
select = m.addVars(num_facilities, vtype=GRB.BINARY, name='select')
assign = m.addVars(cartesian_prod, ub=1, vtype=GRB.CONTINUOUS, name='assign')

# 两个约束条件
m.addConstrs((assign[c, f] <= select[f] for c, f in cartesian_prod), name='Setup2ship')
m.addConstrs((gp.quicksum(assign[c, f] for f in range(num_facilities)) == 1 for c in range(num_customers)),
             name='demand')

# 目标函数
m.setObjective(select.prod(setup_cost) + assign.prod(shipping_cost), GRB.MINIMIZE)

# 优化
m.optimize()

# 04 结果分析
print("——————————————————以下为模型的结果分析——————————————————")
# 04-01 接下来看一下仓库修建选址决策：在位置4修建一个仓库
print("接下来看一下仓库修建选址决策：在位置4修建一个仓库")
for facility in select.keys():
    if (abs(select[facility].x) > 1e-6):
        print(f"\n Build a warehouse at location {facility + 1}.")

print("运输计划表明了从每个修建的设施运送到每个客户的比例：两个超市都由仓库4供货")
# 04-02 运输计划表明了从每个修建的设施运送到每个客户的比例：两个超市都由仓库4供货
for customer, facility in assign.keys():
    if (abs(assign[customer, facility].x)) > 1e-6:
        print(
            f"\n Supermarket {customer + 1} receives {round(100 * assign[customer, facility].x, 2)} % of its demand  from Warehouse {facility + 1} .")

print("——————————————————模型结果分析结束——————————————————")
